# -*- coding: utf-8 -*-

from datetime import datetime
import time

from flask import current_app

from mongoengine import Document, DynamicDocument, fields, queryset_manager
from mongoengine.queryset.visitor import Q
from flask_restful import fields as rest_fields

from utils.auth import current_user

TARGET_STATUS = (
    (-1, 'Newly added'),
    (0, 'Disabled'),
    (1, 'Waiting'),
    (2, 'Scraping')
)
SUBSCRIPTION_TYPE = (
    'publisher',
    'hashtag',
    'place',
)


class Graph(DynamicDocument):
    id_ = fields.StringField(db_field='_id', primary_key=True)
    caption = fields.StringField() # some graph may not have caption attr
    date = fields.IntField(required=True)
    publisher_id = fields.StringField(db_field='owner_id', required=True)
    publisher = fields.DictField(db_field='owner')
    typename = fields.StringField()
    children = fields.ListField(fields.DictField())
    liked_by = fields.ListField(db_field='likedBy', default=list)
    liked_count = fields.IntField(default=0)
    location = fields.DictField()
    hashtags = fields.ListField(default=list)
    status = fields.IntField()

    meta = {
        'collection': 'graphimage'
    }

    @queryset_manager
    def objects(cls, queryset):
        return queryset.filter(publisher_id__nin=current_user.excluded_publishers, status__ne=0)

    @queryset_manager
    def objects_subscribed(cls, queryset):
        subscriptions = Subscription.objects(
            openid=current_user.openid,
            status__ne=0
        )
        subscribed_publishers = [s.target_id for s in subscriptions if s.type_ == 'publisher']
        subscribed_hashtags = [s.target_id for s in subscriptions if s.type_ == 'hashtag']
        return queryset.filter(publisher_id__nin=current_user.excluded_publishers, status__ne=0).filter(
            Q(publisher_id__in=subscribed_publishers) | Q(hashtags__in=subscribed_hashtags)
        )


class Publisher(DynamicDocument):
    id_ = fields.StringField(db_field='_id', primary_key=True)
    username = fields.StringField(required=True)
    fullname = fields.StringField(db_field='full_name')
    # avatar_info = fields.DictField(db_field='downloaded_avatar_info')
    followed_by = fields.IntField()
    biography = fields.StringField()
    published_count = fields.IntField()
    status = fields.IntField(choices=TARGET_STATUS, default=-1)
    created_ts = fields.IntField(required=True, default=lambda: int(time.time()))
    meta = {
        'collection': 'publisher'
    }

    @classmethod
    def get_instance(cls, **kwargs):
        id_ = kwargs['id_']
        publisher = cls.objects(pk=id_).first()
        if publisher is None:
            publisher = cls(**kwargs)
            publisher.save()
        return publisher

    def enable(self):
        self.status = 1
        self.save()
        try:
            current_app.redis.enable_publisher(self)
        except:
            self.status = 0
            self.save()
            raise

    def disable(self):
        self.status = 0
        self.save()
        try:
            current_app.redis.disable_publisher(self)
        except:
            self.status = 1
            self.save()
            raise


class Hashtag(DynamicDocument):
    name = fields.StringField(required=True)
    published_count = fields.IntField()
    top_posts = fields.ListField(fields.DictField())
    status = fields.IntField(choices=TARGET_STATUS, default=-1)
    created_ts = fields.IntField(required=True, default=lambda: int(time.time()))

    @classmethod
    def get_instance(cls, **kwargs):
        name = kwargs['name']
        hashtag = cls.objects(name=name).first()
        if hashtag is None:
            hashtag = cls(**kwargs)
            hashtag.save()
        return hashtag

    def enable(self):
        self.status = 1
        self.save()
        try:
            current_app.redis.enable_hashtag(self)
        except:
            self.status = 0
            self.save()
            raise

    def disable(self):
        self.status = 0
        self.save()
        try:
            current_app.redis.disable_hashtag(self)
        except:
            self.status = 1
            self.save()
            raise


class User(Document):
    openid = fields.StringField(required=True)
    unionid = fields.StringField()
    session_key = fields.StringField(db_field='sessionKey', required=True)
    nickname = fields.StringField()
    avatar_url = fields.StringField(db_field='avatarUrl')
    gender = fields.StringField()
    city = fields.StringField()
    province = fields.StringField()
    country = fields.StringField()
    language = fields.StringField()
    first_login_time = fields.DateTimeField()
    last_login_time = fields.DateTimeField()
    last_visit = fields.DateTimeField()
    liked_graph = fields.ListField(db_field='likedGraph', default=list)
    excluded_publishers = fields.ListField(db_field='excludedPublishers', default=list)
    status = fields.IntField(required=True, default=1)
    created_ts = fields.IntField(required=True, default=lambda: int(time.time()))

    @classmethod
    def login(cls, session_info):
        try:
            user = cls.objects.get(openid=session_info['openid'])
        except:
            user = cls(
                openid = session_info['openid'],
                unionid = session_info.get('unionid'),
                session_key = session_info['session_key'],
                first_login_time = datetime.now(),
            )
        else:
            user.session_key = session_info['session_key']
        finally:
            user.last_login_time = datetime.now()
            user.save()
        return user

    @classmethod
    def get_anonymous(cls):
        return User(openid='anonymous', session_key='anonymous')

    def update_profile(self, **kwargs):
        self.update(**kwargs)
        self.save()

    def subscribe(self, type_, target):
        if type_ == 'publisher':
            target_id = target.id_
        elif type_ == 'hashtag':
            target_id = target.name
        else:
            target_id = target.id_
        s = Subscription.objects(
            openid=self.openid,
            type_=type_,
            target_id=target_id
        ).first()
        if s is None:
            s = Subscription(
                openid=self.openid,
                type_=type_,
                target_id=target_id
            )
        s.status = 1
        s.save()

    def unsubscribe(self, id_):
        s = Subscription.objects(pk=id_).first()
        if s is None:
            return None
        s.status = 0
        s.save()
        return s

    def liked(self, graph_id):
        graph = Graph.objects(pk=graph_id).first()
        if graph is None:
            return None
        self.modify(add_to_set__liked_graph=graph_id)
        graph.modify(add_to_set__liked_by=self.openid, inc__liked_count=1)
        return graph

    def unliked(self, graph_id):
        graph = Graph.objects(pk=graph_id).first()
        if graph is None:
            return None
        self.modify(pull__liked_graph=graph_id)
        graph.modify(pull__liked_by=self.openid, dec__liked_count=1)
        return graph

    def exclude(self, publisher_id):
        publisher = Publisher.objects(pk=publisher_id).first()
        if publisher is None:
            return None
        self.modify(add_to_set__excluded_publishers=publisher.id_)
        Subscription.objects(
            openid=current_user.openid,
            type_='publisher',
            target_id=publisher_id
        ).modify(set__status=0)
        return publisher

    def include(self, publisher_id):
        publisher = Publisher.objects(pk=publisher_id).first()
        if publisher is None:
            return None
        self.modify(pull__excluded_publishers=publisher.id_)
        return publisher


class Subscription(Document):
    openid = fields.StringField(required=True)
    type_ = fields.StringField(db_field='type', required=True, choices=SUBSCRIPTION_TYPE)
    target_id = fields.StringField(required=True)
    status = fields.IntField(required=True, default=1)
    created_ts = fields.IntField(required=True, default=lambda: int(time.time()))

    @property
    def target(self):
        if self.type_ == 'publisher':
            publisher = Publisher.objects(pk=self.target_id).first()
            if publisher is None:
                return None
            return {
                'id': publisher.id_,
                'username': publisher.username,
                'fullname': publisher.fullname
            }
        elif self.type_ == 'hashtag':
            hashtag = Hashtag.objects(name=self.target_id).first()
            if hashtag is None:
                return None
            return {
                'name': hashtag.name
            }
        else:
            return None


class Marshal(object):
    graph = {
        'id_': rest_fields.String,
        'caption': rest_fields.String,
        'date': rest_fields.Integer,
        'publisher': rest_fields.Raw,
        'typename': rest_fields.String,
        'children': rest_fields.Raw,
        'liked': rest_fields.Boolean(attribute=lambda g: current_user.openid in g.liked_by),
        'likedCount': rest_fields.Integer(attribute='liked_count'),
        'location': rest_fields.Raw,
    }
    publisher = {
        'id_': rest_fields.String,
        'username': rest_fields.String,
        'fullname': rest_fields.String,
        'followed_by': rest_fields.Integer,
        'biography': rest_fields.String,
        'published_count': rest_fields.Integer,
        'is_subscribed': rest_fields.Boolean,
        'subscription_id': rest_fields.String,
    }
    hashtag = {
        'name': rest_fields.String,
        'published_count': rest_fields.Integer,
        # 'top_posts': rest_fields.Raw,
    }
    subscription = {
        'id_': rest_fields.String(attribute='pk'),
        'type': rest_fields.String(attribute='type_'),
        'target_id': rest_fields.String,
        'target': rest_fields.Raw(attribute='target')
    }
